/*
 * ThermistorTable.cpp
 *
 * Copyright 2012 Pieter Agten
 *
 * This file is part of tltgen.
 *
 * tltgen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * tltgen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with tltgen.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   ThermistorTable.cpp
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   30 sep 2012
 */


#include <list>
#include <iostream>
#include <stdexcept>
#include <boost/bind.hpp>

#include "Math.hpp"
#include "ThermistorTableRow.hpp"
#include "GslLeastSquaresSolver.hpp"
#include "MpfitLeastSquaresSolver.hpp"

#include "ThermistorTable.hpp"

using boost::lexical_cast;
using boost::signals2::connection;

namespace tltgen {

  ThermistorTable::ThermistorTable() :
    batchCount(0)
  {
    for (int i = 0; i < ThermistorTableRow::NB_COLUMNS; ++i) {
      this->minRows[i] = NULL;
      this->maxRows[i] = NULL;
    }
  }

  ThermistorTable::~ThermistorTable()
  {
    for (std::list<ThermistorTableRow *>::iterator it = this->rows.begin(); it != this->rows.end(); ++it) {
      delete *it;
    }
  }

  // Circuit model
  bool ThermistorTable::isValidCircuit(const shared_ptr<Circuit const>& circuit) const
  {
    return circuit.get() != NULL;
  }
  void ThermistorTable::setCircuit(const shared_ptr<Circuit const>& circuit)
  {
    if (! isValidCircuit(circuit)) {
      throw std::invalid_argument("Invalid circuit specified."); 
    }
    this->circuit = circuit;

    beginBatch();
    for (std::list<ThermistorTableRow *>::iterator it = this->rows.begin(); it != this->rows.end(); ++it) {
      (*it)->recalculateVoltageAdc();
    }
    endBatch();
  }
  shared_ptr<Circuit const> ThermistorTable::getCircuit() const
  {
    return this->circuit;
  }
  bool ThermistorTable::hasCircuit() const
  {
    return this->circuit.get() != NULL;
  }

  // ADC model
  bool ThermistorTable::isValidAnalogToDigitalConverter(const shared_ptr<AnalogToDigitalConverter const>& adc) const
  {
    return adc.get() != NULL;
  }
  void ThermistorTable::setAnalogToDigitalConverter(const shared_ptr<AnalogToDigitalConverter const>& adc)
  {
    if (! isValidAnalogToDigitalConverter(adc)) {
      throw std::invalid_argument("Invalid analog to digital converter specified.");
    }
    this->adc = adc;

    beginBatch();
    for (std::list<ThermistorTableRow *>::iterator it = this->rows.begin(); it != this->rows.end(); ++it) {
      (*it)->recalculateVoltageAdc();
    }
    endBatch();
  }
  shared_ptr<AnalogToDigitalConverter const> ThermistorTable::getAnalogToDigitalConverter() const
  {
    return this->adc;
  }
  bool ThermistorTable::hasAnalogToDigitalConverter() const
  {
    return this->adc.get() != NULL;
  }

  bool ThermistorTable::hasColumn(ThermistorTableRow::Column col) const
  {
    switch (col) {
    case ThermistorTableRow::TEMPERATURE:
      return true;
    case ThermistorTableRow::RESISTANCE:
      return true;
    case ThermistorTableRow::VOLTAGE:
      return hasCircuit();
    case ThermistorTableRow::ADCVALUE:
      return hasCircuit() && hasAnalogToDigitalConverter();
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }

  std::string ThermistorTable::getColumnName(ThermistorTableRow::Column col) const
  {
    switch (col) {
    case ThermistorTableRow::TEMPERATURE:
      return "Temperature";
    case ThermistorTableRow::RESISTANCE:
      return "Resistance";
    case ThermistorTableRow::VOLTAGE:
      return "Voltage";
    case ThermistorTableRow::ADCVALUE:
      return "ADC Value";
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }

  bool ThermistorTable::isValidValue(ThermistorTableRow::Column col, double v) const
  {
    switch (col) {
    case ThermistorTableRow::TEMPERATURE:
      return isValidTemperature(v);
    case ThermistorTableRow::RESISTANCE:
      return isValidResistance(v);
    case ThermistorTableRow::VOLTAGE:
      return isValidVoltage(v);
    case ThermistorTableRow::ADCVALUE:
      return isValidAdcValue(v);
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }
  bool ThermistorTable::isValidTemperature(Temperature t) const
  {
    return tltgen::isValidKelvinTemperature(t);
  }
  bool ThermistorTable::isValidResistance(Resistance r) const
  {
    return tltgen::isValidResistance(r) && 
      (! hasCircuit() || getCircuit()->isValidThermistorResistance(r)) &&
      (! hasCircuit() || ! hasAnalogToDigitalConverter() || getAnalogToDigitalConverter()->isValidInputVoltage(getCircuit()->getOutputVoltage(r)));
  }
  bool ThermistorTable::isValidVoltage(Voltage v) const
  {
    return tltgen::isValidVoltage(v) &&
      (! hasCircuit() || getCircuit()->isValidOutputVoltage(v)) && 
      (! hasAnalogToDigitalConverter() || getAnalogToDigitalConverter()->isValidInputVoltage(v));
  }
  bool ThermistorTable::isValidAdcValue(AdcValue a) const
  {
    return tltgen::isValidAdcValue(a) && 
      (! hasAnalogToDigitalConverter() || getAnalogToDigitalConverter()->isValidOutputValue(a)) &&
      (! hasAnalogToDigitalConverter() || ! hasCircuit() || getCircuit()->isValidOutputVoltage(getAnalogToDigitalConverter()->getInputVoltage(a)));
  }
  

  void ThermistorTable::beginBatch()
  {
    this->batchCount += 1;
  }
  void ThermistorTable::endBatch()
  {
    if (this->batchCount <= 0) {
      throw std::runtime_error("Cannot end batch because batch count is already 0.");
    }
    
    this->batchCount -= 1;
    if (this->batchCount == 0) {
      findBounds();
      tableChanged(BATCH_ENDED, 0);
    }
  }
  ThermistorTableRow *ThermistorTable::createRow()
  {
    ThermistorTableRow *row = new ThermistorTableRow(this);
    observeRow(row);
    this->rows.push_back(row);
    tableChanged(ROW_ADDED, row);
    return row;
  }

  void ThermistorTable::removeRow(ThermistorTableRow *row)
  {
    row->remove();
    this->rows.remove(row);
    for (int i = 0; i < ThermistorTableRow::NB_COLUMNS; ++i) {
      if (row == this->minRows[i] || row == this->maxRows[i]) {
	findBounds();
	return;
      }
    }
    tableChanged(ROW_REMOVED, row);
    delete(row);
  }
  unsigned int ThermistorTable::getNbRows() const
  {
    return this->rows.size();
  }

  unsigned int ThermistorTable::getNbValidRows() const
  {
    //TODO: cache result!
    unsigned int count = 0;
    for (std::list<ThermistorTableRow *>::const_iterator it = this->rows.begin(); it != this->rows.end(); ++it) {
      if ((*it)->isValid()) {
	count += 1;
      }
    }
    return count;
  }

  std::list<ThermistorTableRow *>::const_iterator ThermistorTable::begin() const
  {
    return this->rows.begin();
  }
  std::list<ThermistorTableRow *>::const_iterator ThermistorTable::end() const
  {
    return this->rows.end();
  }
  std::list<ThermistorTableRow *>::iterator ThermistorTable::begin()
  {
    return this->rows.begin();
  }
  std::list<ThermistorTableRow *>::iterator ThermistorTable::end()
  {
    return this->rows.end();
  }
  
  tuple<Temperature,Temperature> ThermistorTable::getTemperatureBounds() const
  {
    ThermistorTableRow *minRow = this->minRows[ThermistorTableRow::TEMPERATURE];
    ThermistorTableRow *maxRow = this->maxRows[ThermistorTableRow::TEMPERATURE];
    if (minRow == NULL || maxRow == NULL) {
      return tuple<Temperature,Temperature>(0,1);
    }
    return tuple<Temperature,Temperature>(minRow->getTemperature(), maxRow->getTemperature());
  }
  tuple<Resistance,Resistance> ThermistorTable::getResistanceBounds() const
  {
    ThermistorTableRow *minRow = this->minRows[ThermistorTableRow::RESISTANCE];
    ThermistorTableRow *maxRow = this->maxRows[ThermistorTableRow::RESISTANCE];
    if (minRow == NULL || maxRow == NULL) {
      return tuple<Resistance,Resistance>(1,2);
    }
    return tuple<Resistance,Resistance>(minRow->getResistance(), maxRow->getResistance());
  }
  tuple<Voltage,Voltage> ThermistorTable::getVoltageBounds() const
  {
    ThermistorTableRow *minRow = this->minRows[ThermistorTableRow::VOLTAGE];
    ThermistorTableRow *maxRow = this->maxRows[ThermistorTableRow::VOLTAGE];
    if (minRow == NULL || maxRow == NULL) {
      return tuple<Voltage,Voltage>(1,2);
    }
    return tuple<Voltage,Voltage>(minRow->getVoltage(), maxRow->getVoltage());
  }
  tuple<AdcValue,AdcValue> ThermistorTable::getAdcValueBounds() const
  {
    ThermistorTableRow *minRow = this->minRows[ThermistorTableRow::ADCVALUE];
    ThermistorTableRow *maxRow = this->maxRows[ThermistorTableRow::ADCVALUE];
    if (minRow == NULL || maxRow == NULL) {
      return tuple<int,int>(0,1);
    }
    return tuple<AdcValue,AdcValue>(minRow->getAdcValue(), maxRow->getAdcValue());
  }
  tuple<double,double> ThermistorTable::getBounds(ThermistorTableRow::Column col) const
  {
    if (this->minRows[col] == NULL || this->maxRows[col] == NULL) {
      return tuple<double,double>(INFINITY,-INFINITY);
    }

    return tuple<double,double>(minRows[col]->getValue(col),maxRows[col]->getValue(col));
  }
  
  connection ThermistorTable::connectTableChangedObserver(const Slot &slot)
  {
    return this->tableChanged.connect(slot);
  }

  // Protected functions below
  void ThermistorTable::observeRow(ThermistorTableRow *row)
  {
    row->connectObserver(boost::bind(&ThermistorTable::onRowChange, this, _1, _2, _3));
  }
  void ThermistorTable::observeAllRows()
  {
    for (std::list<ThermistorTableRow *>::iterator it = this->rows.begin(); it != this->rows.end(); ++it) {
      observeRow(*it);
    }
  }

  void ThermistorTable::onRowChange(ThermistorTableRow::Event e, ThermistorTableRow *row, ThermistorTableRow::Column col)
  {
    if (batchCount > 0) {
      return;
    }

    switch (e) {
    case ThermistorTableRow::COLUMN_CHANGED:
      if (row == this->minRows[col] || row == this->maxRows[col]) {
	findBounds(col);
      } else if (row->hasValue(col)) {
	if (row->getValue(col) < getBounds(col).get<0>()) {
	  this->minRows[col] = row;
	}
	if (row->getValue(col) > getBounds(col).get<1>()) {
	  this->maxRows[col] = row;
	}
      }
      break;
    case ThermistorTableRow::ROW_CHANGED:
      tableChanged(ROW_CHANGED, row);
      break;
    default:
      break;
    }
  }

  void ThermistorTable::findBounds()
  {
    for (int col = 0; col < ThermistorTableRow::NB_COLUMNS; ++col) {
      findBounds((ThermistorTableRow::Column)col);
    }
  }

  void ThermistorTable::findBounds(ThermistorTableRow::Column col)
  {
    if (this->batchCount > 0) {
      return;
    }
    ThermistorTableRow *firstValidRow = NULL;
    std::list<ThermistorTableRow *>::const_iterator it = this->rows.begin();
    while (firstValidRow == NULL && it != this->rows.end()) {
      if ((*it)->hasValidValue(col)) {
	firstValidRow = *it;
      }
      ++it;
    }
    
    this->minRows[col] = firstValidRow;
    this->maxRows[col] = firstValidRow;
    if (firstValidRow != NULL) {
      for (std::list<ThermistorTableRow *>::const_iterator it = this->rows.begin(); it != this->rows.end(); ++it) {
	if ((*it)->hasValidValue(col)) {
	  if ((*it)->getValue(col) < this->minRows[col]->getValue(col)) {
	    this->minRows[col] = *it;
	  }
	  if ((*it)->getValue(col) > this->maxRows[col]->getValue(col)) {
	    this->maxRows[col] = *it;
	  }
	}
      }
    }

  }

  /*void ThermistorTable::updateThermistorModels()
  {
    if (this->batchCount > 0) {
      return;
    }
    // Beta model:
    BetaModelGenerator betaGenerator;
    BetaModel *betaModel = (BetaModel *)generateThermistorModel(&betaGenerator);
    if (this->betaModel != NULL) {
      delete this->betaModel;
    }
    this->betaModel = betaModel;

    // Steinhart-Hart model:
    SteinhartHartModelGenerator shhGenerator;
    SteinhartHartModel *shhModel = (SteinhartHartModel *)generateThermistorModel(&shhGenerator);
    if (this->shhModel != NULL) {
      delete this->shhModel;
    }
    this->shhModel = shhModel;

    tableChanged(BETAMODEL_CHANGED, NULL);
    tableChanged(SHHMODEL_CHANGED, NULL);
    }*/
  /*ThermistorModel *ThermistorTable::generateThermistorModel(ThermistorGenerator *generator) const
  {
    ThermistorModel *result = NULL;
    generator->clearValues();
    try {
      for (std::list<ThermistorTableRow *>::const_iterator ci = this->rows.begin(); ci != this->rows.end(); ++ci) {
        generator->addValue((*ci)->getTemperature(), (*ci)->getResistance());
      }
      //TODO: use settable solver instead of hardcoded one!
      //GslLeastSquaresSolver solver;
      MpfitLeastSquaresSolver solver;
      result = generator->generate(solver);
    } catch (const std::exception& e) {
      std::cout << "Unable to generate thermistor model: " << e.what() << std::endl;
    }
    return result;
    }*/
  
  /*ThermistorModel *ThermistorTable::getModelRaw(Model model) const
  {
    switch(model) {
    case BETA:
      return getBetaModelRaw();
    case SHH:
      return getShhModelRaw();
    }
    return NULL;
  }
  BetaModel *ThermistorTable::getBetaModelRaw() const
  {
    return this->betaModel;
  }
  SteinhartHartModel *ThermistorTable::getShhModelRaw() const
  {
    return this->shhModel;
    }*/
}

//BOOST_CLASS_EXPORT_IMPLEMENT(tltgen::ThermistorTable)


